package com.ccrfid.rmc6.service.business.visitroom;

import com.ccrfid.rmc6.entity.business.visitroom.RoomDutyVisitorEntity;
import com.ccrfid.rmc6.entity.business.visitroom.RoomVisitAreaOverallEntity;
import com.ccrfid.rmc6.entity.business.visitroom.RoomVisitRecordEntity;
import com.ccrfid.rmc6.entity.business.visitroom.RoomVisitRuleEntity;
import com.ccrfid.rmc6.entity.personnel.PersonnelMoveEntity;
import com.ccrfid.rmc6.entity.poi.PoiLocationEntity;
import com.ccrfid.rmc6.entity.sys.OrgEntity;
import com.ccrfid.rmc6.pojo.dto.TimeRecord;
import com.ccrfid.rmc6.pojo.dto.visitroom.*;
import com.ccrfid.rmc6.repository.business.visitroom.RoomDutyVisitorRepository;
import com.ccrfid.rmc6.repository.business.visitroom.RoomVisitAreaOverallRepository;
import com.ccrfid.rmc6.repository.business.visitroom.RoomVisitRecordRepository;
import com.ccrfid.rmc6.repository.business.visitroom.RoomVisitRuleRepository;
import com.ccrfid.rmc6.repository.personnel.PersonnelMoveRepository;
import com.ccrfid.rmc6.repository.poi.PoiLocationRepository;
import com.ccrfid.rmc6.service.sys.OrgService;
import com.ccrfid.rmc6.service.sys.RoomService;
import com.ccrfid.rmc6.util.DateUtil;
import com.ccrfid.rmc6.util.GeomUtil;
import com.vividsolutions.jts.geom.Point;
import org.hibernate.query.NativeQuery;
import org.hibernate.transform.Transformers;
import org.hibernate.type.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;

@Service
@Transactional
public class VisitRoomServiceImpl implements VisitRoomService {
    private static final Logger logger = LoggerFactory.getLogger(VisitRoomServiceImpl.class);
    @PersistenceContext
    private EntityManager entityManager;
    @Autowired
    private OrgService orgService;
    @Autowired
    private RoomVisitRecordRepository roomVisitRecordRepository;
    @Autowired
    private RoomVisitRuleRepository roomVisitRuleRepository;
    @Autowired
    private RoomDutyVisitorRepository roomDutyVisitorRepository;
    @Autowired
    private RoomVisitAreaOverallRepository roomVisitAreaOverallRepository;
    @Autowired
    private PersonnelMoveRepository personnelMoveRepository;
    @Autowired
    private RoomService roomService;
    @Autowired
    private PoiLocationRepository poiLocationRepository;

    @Override
    @Scheduled(cron = "0 0 1 * * ?")
    //Scheduled(cron = "秒 分 时 日 月 星期")
    //定时任务，每天凌晨1点钟执行一次
    public void scheduleTask() {
        doTask();
        logger.debug("do scheduleTask finish.");
    }

    private void doTask() {
        List<RoomVisitRuleEntity> ruleEntities = roomVisitRuleRepository.getAllBy();
        for (RoomVisitRuleEntity ruleEntity : ruleEntities) {
            Date startTime = getYesterdayDateTime(ruleEntity.getStartTime());
            Date stopTime = getYesterdayDateTime(ruleEntity.getStopTime());
            //生成下监室记录
            generateVisitRecord2(startTime, stopTime);
        }
        //生成监区下监室完成率统计中间结果
        java.sql.Date yesterday = new java.sql.Date(DateUtil.getOneDayBefore(new Date()).getTime());
        generateRoomVisitAreaOverall(yesterday);
    }

    private void generateRoomVisitAreaOverall(java.sql.Date date) {
        List<VisitRoomResultDto> list = getVisitRoomStatistics(date.toString());
        for (VisitRoomResultDto dto : list) {
            RoomVisitRuleEntity roomVisitRule = dto.getRoomVisitRule();
            List<PrisonAreaDto> prisonAreaList = dto.getPrisonAreaList();
            for (PrisonAreaDto prisonArea : prisonAreaList) {
                RoomVisitAreaOverallEntity areaOverall = new RoomVisitAreaOverallEntity();
                areaOverall.setDutyDate(date);
                areaOverall.setTimeType(roomVisitRule.getTimeType());
                areaOverall.setOrgId(prisonArea.getOrgId());
                areaOverall.setOrgSn(prisonArea.getOrgSn());
                areaOverall.setOrgName(prisonArea.getOrgName());
                areaOverall.setSortNumber(prisonArea.getSortNumber());
                areaOverall.setCompletionRate(prisonArea.getCompletionRate());
                roomVisitAreaOverallRepository.save(areaOverall);
            }
        }
    }

    private Date getYesterdayDateTime(Time time) {
        Date date = DateUtil.getOneDayBefore(new Date());
        return DateUtil.mergeDateAndTime(date, time);
    }

    @Deprecated
    private void generateVisitRecord(Date startTime, Date stopTime) {
        List<OrgEntity> prisonRooms = orgService.getAllPrisonRooms();
        for (OrgEntity orgEntity : prisonRooms) {
            Set<Integer> locatorSet = roomService.getLocatorInRoom(orgEntity.getId());
            if (locatorSet.size() > 0) {
                List<RoomDutyVisitorEntity> dutyVisitors = roomDutyVisitorRepository.getAllByOrgId(orgEntity.getId());
                for (RoomDutyVisitorEntity dutyVisitor : dutyVisitors) {
                    generateVisitRoomRecord(orgEntity.getId(), locatorSet, dutyVisitor.getPersonnelId(), startTime, stopTime);
                }
            }
        }
    }

    private void generateVisitRecord2(Date startTime, Date stopTime) {
        List<OrgEntity> prisonRooms = orgService.getAllPrisonRooms();
        for (OrgEntity orgEntity : prisonRooms) {
            List<PoiLocationEntity> locationList = poiLocationRepository.getByBindOrgId(orgEntity.getId());
            if (locationList != null && locationList.size() > 0) {
                List<RoomDutyVisitorEntity> dutyVisitors = roomDutyVisitorRepository.getAllByOrgId(orgEntity.getId());
                for (RoomDutyVisitorEntity dutyVisitor : dutyVisitors) {
                    generateVisitRoomRecord(orgEntity.getId(), locationList, dutyVisitor.getPersonnelId(), startTime, stopTime);
                }
            }
        }
    }

    private boolean locationContainsPos(List<PoiLocationEntity> locationList, Integer floorNumber, Point pos) {
        if (pos == null) {
            return false;
        }
        for (PoiLocationEntity locationEntity : locationList) {
            if (locationEntity.getLocationPolygon().contains(pos)
                    && locationEntity.getFloorNumber().equals(floorNumber)) {
                return true;
            }
        }
        return false;
    }

    private void generateVisitRoomRecord(Long orgId, List<PoiLocationEntity> locationList, Long personnelId, Date startTime, Date stopTime) {
        List<PersonnelMoveEntity> moveList =
                personnelMoveRepository.getAllByPersonnelIdAndRecordTimeBetween(personnelId, startTime, stopTime);
        if (moveList != null && moveList.size() > 0) {
            List<TimeRecord> list = new ArrayList<>();
            for (PersonnelMoveEntity moveEntity : moveList) {
                Point currentPos = GeomUtil.INSTANCE.createPoint(moveEntity.getCurrentX(), moveEntity.getCurrentY());
                Point frontPos = GeomUtil.INSTANCE.createPoint(moveEntity.getFrontX(), moveEntity.getFrontY());
                if (!locationContainsPos(locationList, moveEntity.getFrontFloorNumber(), frontPos)
                        && locationContainsPos(locationList, moveEntity.getCurrentFloorNumber(), currentPos)) {
                    list.add(new TimeRecord(moveEntity.getRecordTime(), null));
                }
                if (locationContainsPos(locationList, moveEntity.getFrontFloorNumber(), frontPos)
                        && locationContainsPos(locationList, moveEntity.getCurrentFloorNumber(), currentPos)) {
                    if (list.size() > 0) {
                        TimeRecord timeRecord = list.get(list.size() - 1);
                        timeRecord.setStopTime(moveEntity.getRecordTime());
                    } else {
                        list.add(new TimeRecord(new Timestamp(startTime.getTime()), moveEntity.getRecordTime()));
                    }
                }
                if (locationContainsPos(locationList, moveEntity.getFrontFloorNumber(), frontPos)
                        && !locationContainsPos(locationList, moveEntity.getCurrentFloorNumber(), currentPos)) {
                    if (list.size() > 0) {
                        TimeRecord timeRecord = list.get(list.size() - 1);
                        timeRecord.setStopTime(moveEntity.getRecordTime());
                    } else {
                        list.add(new TimeRecord(new Timestamp(startTime.getTime()), moveEntity.getRecordTime()));
                    }
                }
            }
            if (list.size() > 0) {
                TimeRecord timeRecord = list.get(list.size() - 1);
                if (timeRecord.getStopTime() == null) {
                    timeRecord.setStopTime(new Timestamp(stopTime.getTime()));
                }
            }
            //保存某个警员的下监室记录
            for (TimeRecord timeRecord : list) {
                RoomVisitRecordEntity recordEntity = new RoomVisitRecordEntity();
                recordEntity.setOrgId(orgId);
                recordEntity.setPersonnelId(personnelId);
                recordEntity.setEnterTime(timeRecord.getStartTime());
                recordEntity.setLeaveTime(timeRecord.getStopTime());
                recordEntity.setStayTime(timeRecord.getSeconds());
                roomVisitRecordRepository.save(recordEntity);
            }
        }
    }

    private void generateVisitRoomRecord(Long orgId, Set<Integer> locatorSet, Long personnelId, Date startTime, Date stopTime) {
        List<PersonnelMoveEntity> moveList =
                personnelMoveRepository.getAllByPersonnelIdAndRecordTimeBetween(personnelId, startTime, stopTime);
        if (moveList != null && moveList.size() > 0) {
            List<TimeRecord> list = new ArrayList<>();
            for (PersonnelMoveEntity moveEntity : moveList) {
                if (!locatorSet.contains(moveEntity.getFrontLocatorAddress())
                        && locatorSet.contains(moveEntity.getCurrentLocatorAddress())) {
                    list.add(new TimeRecord(moveEntity.getRecordTime(), null));
                }
                if (locatorSet.contains(moveEntity.getFrontLocatorAddress())
                        && locatorSet.contains(moveEntity.getCurrentLocatorAddress())) {
                    if (list.size() > 0) {
                        TimeRecord timeRecord = list.get(list.size() - 1);
                        timeRecord.setStopTime(moveEntity.getRecordTime());
                    } else {
                        list.add(new TimeRecord(new Timestamp(startTime.getTime()), moveEntity.getRecordTime()));
                    }
                }
                if (locatorSet.contains(moveEntity.getFrontLocatorAddress())
                        && !locatorSet.contains(moveEntity.getCurrentLocatorAddress())) {
                    if (list.size() > 0) {
                        TimeRecord timeRecord = list.get(list.size() - 1);
                        timeRecord.setStopTime(moveEntity.getRecordTime());
                    } else {
                        list.add(new TimeRecord(new Timestamp(startTime.getTime()), moveEntity.getRecordTime()));
                    }
                }
            }
            if (list.size() > 0) {
                TimeRecord timeRecord = list.get(list.size() - 1);
                if (timeRecord.getStopTime() == null) {
                    timeRecord.setStopTime(new Timestamp(stopTime.getTime()));
                }
            }
            //保存某个警员的下监室记录
            for (TimeRecord timeRecord : list) {
                RoomVisitRecordEntity recordEntity = new RoomVisitRecordEntity();
                recordEntity.setOrgId(orgId);
                recordEntity.setPersonnelId(personnelId);
                recordEntity.setEnterTime(timeRecord.getStartTime());
                recordEntity.setLeaveTime(timeRecord.getStopTime());
                recordEntity.setStayTime(timeRecord.getSeconds());
                roomVisitRecordRepository.save(recordEntity);
            }
        }
    }

    @Override
    public void updateRoomVisitRecords(java.sql.Date date) {
        //
        deleteRecords(date);
        //
        doTask(date);
        //删除监区下监室完成率统计中间结果
        roomVisitAreaOverallRepository.deleteAllByDutyDate(date);
        //生成监区下监室完成率统计中间结果
        generateRoomVisitAreaOverall(date);
    }

    private void deleteRecords(java.sql.Date date) {
        Date startTime = DateUtil.parseDateTimeStr(date.toString() + " 00:00:00");
        Date stopTime = DateUtil.parseDateTimeStr(date.toString() + " 23:59:59");
        roomVisitRecordRepository.deleteByEnterTimeBetween(startTime, stopTime);
    }

    private void doTask(java.sql.Date date) {
        List<RoomVisitRuleEntity> ruleEntities = roomVisitRuleRepository.getAllBy();
        for (RoomVisitRuleEntity ruleEntity : ruleEntities) {
            Date startTime = DateUtil.mergeDateAndTime(date, ruleEntity.getStartTime());
            Date stopTime = DateUtil.mergeDateAndTime(date, ruleEntity.getStopTime());

            generateVisitRecord2(startTime, stopTime);
        }
    }

    @Override
    public List<VisitRoomResultDto> getVisitRoomStatistics(String dateStr) {
        List<VisitRoomResultDto> list = new ArrayList<>();
        List<RoomVisitRuleEntity> ruleEntities = roomVisitRuleRepository.getAllBy();
        for (RoomVisitRuleEntity ruleEntity : ruleEntities) {
            list.add(createVisitRoomResultDto(ruleEntity, dateStr));
        }
        return list;
    }

    private VisitRoomResultDto createVisitRoomResultDto(RoomVisitRuleEntity ruleEntity, String dateStr) {
        VisitRoomResultDto dto = new VisitRoomResultDto();
        dto.setRoomVisitRule(ruleEntity);

        List<PrisonAreaDto> prisonAreaDtoList = new ArrayList<>();
        List<OrgEntity> prisonAreaList = orgService.getAllPrisonAreas();
        for (OrgEntity prisonArea : prisonAreaList) {
            prisonAreaDtoList.add(createPrisonAreaDto(ruleEntity, prisonArea, dateStr));
        }

        dto.setPrisonAreaList(prisonAreaDtoList);
        dto.calcCompletionRate();
        return dto;
    }

    private PrisonAreaDto createPrisonAreaDto(RoomVisitRuleEntity ruleEntity, OrgEntity prisonArea, String dateStr) {
        PrisonAreaDto dto = new PrisonAreaDto();
        dto.setOrgId(prisonArea.getId());
        dto.setOrgName(prisonArea.getOrgName());
        dto.setOrgSn(prisonArea.getOrgSn());
        dto.setSortNumber(prisonArea.getSortNumber());

        List<PrisonRoomDto> prisonRoomDtoList = new ArrayList<>();
        List<OrgEntity> prisonRoomList = orgService.getAllPrisonRoomsByPid(prisonArea.getId());
        for (OrgEntity prisonRoom : prisonRoomList) {
            prisonRoomDtoList.add(createPrisonRoomDto(ruleEntity, prisonRoom, dateStr));
        }

        dto.setPrisonRoomList(prisonRoomDtoList);
        dto.calcCompletionRate();
        return dto;
    }

    private PrisonRoomDto createPrisonRoomDto(RoomVisitRuleEntity ruleEntity, OrgEntity prisonRoom, String dataStr) {
        PrisonRoomDto dto = new PrisonRoomDto();
        dto.setOrgId(prisonRoom.getId());
        dto.setOrgName(prisonRoom.getOrgName());
        dto.setOrgSn(prisonRoom.getOrgSn());
        dto.setSortNumber(prisonRoom.getSortNumber());

        Date startTime = DateUtil.parseDateTimeStr(dataStr + " " + ruleEntity.getStartTime().toString());
        Date stopTime = DateUtil.parseDateTimeStr(dataStr + " " + ruleEntity.getStopTime().toString());
        List<VisitRecordDto> visitRecordList = getVisitRecordList(prisonRoom.getId(), startTime, stopTime);

        dto.setVisitRecordList(visitRecordList);
        dto.calcCompletionRate(ruleEntity.getMinStayTime(), ruleEntity.getMinFrequency());
        return dto;
    }

    private List<VisitRecordDto> getVisitRecordList(Long orgId, Date startTime, Date stopTime) {
        String sql = "select r.personnel_id as personnelId, p.personnel_name as personnelName, " +
                " r.enter_time as enterTime, r.leave_time as leaveTime, r.stay_time as stayTime " +
                " from room_visit_record r left join personnel p on r.personnel_id = p.id " +
                " where r.org_id = :roomId and (r.enter_time between :startT and :stopT) ";
        List<VisitRecordDto> list = entityManager.createNativeQuery(sql)
                .setParameter("roomId", orgId)
                .setParameter("startT", startTime)
                .setParameter("stopT", stopTime)
                .unwrap(NativeQuery.class)
                .addScalar("personnelId", LongType.INSTANCE)
                .addScalar("personnelName", StringType.INSTANCE)
                .addScalar("enterTime", TimestampType.INSTANCE)
                .addScalar("leaveTime", TimestampType.INSTANCE)
                .addScalar("stayTime", IntegerType.INSTANCE)
                .setResultTransformer(Transformers.aliasToBean(VisitRecordDto.class))
                .list();
        return list;
    }

    @Override
    public List<RoomVisitAreaOverallEntity> getRoomVisitAreaCompletionRate(java.sql.Date date) {
        return roomVisitAreaOverallRepository.getAllByDutyDate(date);
    }

    @Override
    public List<VisitRoomCompletionRateDto> getRoomVisitAverageCompletionRate(java.sql.Date startDate,
                                                                              java.sql.Date stopDate) {
        String sql = "select duty_date as dutyDate, time_type as timeType, avg(completion_rate) as completionRate " +
                " from room_visit_area_overall " +
                " where duty_date between :startD and :stopD " +
                " group by duty_date, time_type";
        List<VisitRoomCompletionRateDto> list = entityManager.createNativeQuery(sql)
                .setParameter("startD", startDate)
                .setParameter("stopD", stopDate)
                .unwrap(NativeQuery.class)
                .addScalar("dutyDate", DateType.INSTANCE)
                .addScalar("timeType", IntegerType.INSTANCE)
                .addScalar("completionRate", DoubleType.INSTANCE)
                .setResultTransformer(Transformers.aliasToBean(VisitRoomCompletionRateDto.class))
                .list();
        return list;
    }
}
